﻿using CodeDeeds.Reflection.Cloning;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace CloneFactoryTest
{
   [TestClass]
   public class UnitTests
   {
      private static Person burgerJoe = new Person()
      {
         FirstName = "Joe",
         LastName = "White",
         NickName = "Burger Joe",
         DateOfBirth = new DateTime(1971,1,1)
      };

      private static Person giJane = new Person()
      {
         FirstName = "Jane",
         LastName = "White",
         NickName = "GI Jane",
         DateOfBirth = new DateTime(1971, 5, 17)
      };

      private static Person babyWiliam = new Person()
      {
         FirstName = "Wiliam",
         LastName = "White",
         NickName = "Baby Wiliam",
         DateOfBirth = new DateTime(1971, 1, 1)
      };

      private static Person babyEllen = new Person()
      {
         FirstName = "Ellen",
         LastName = "White",
         NickName = "Baby Ellen",
         DateOfBirth = new DateTime(1971, 5, 17)
      };

      private static Person oldBob = new Person()
      {
         FirstName = "Bob",
         LastName = "White",
         NickName = "Old Bob",
         DateOfBirth = new DateTime(1938, 3, 15)
      };

      private static Person oldKate = new Person()
      {
         FirstName = "Kate",
         LastName = "White",
         NickName = "Old Kate",
         DateOfBirth = new DateTime(1942, 8, 8)
      };

      private static Person oldArnold = new Person()
      {
         FirstName = "Arnold",
         LastName = "Brown",
         NickName = "Old Arnold",
         DateOfBirth = new DateTime(1940, 4, 15)
      };

      private static Person oldAmber = new Person()
      {
         FirstName = "Amber",
         LastName = "Brown",
         NickName = "Old Amber",
         DateOfBirth = new DateTime(1944, 5, 8)
      };

      static UnitTests()
      {
         burgerJoe.Spouse = giJane;
         giJane.Spouse = burgerJoe;
         burgerJoe.Father = oldBob;
         burgerJoe.Mother = oldKate;
         giJane.Mother = oldAmber;
         giJane.Father = oldArnold;
         burgerJoe.Children.Add(babyWiliam);
         burgerJoe.Children.Add(babyEllen);
         giJane.Children.Add(babyWiliam);
         giJane.Children.Add(babyEllen);
         babyEllen.Mother = giJane;
         babyEllen.Father = burgerJoe;
         babyWiliam.Mother = giJane;
         babyWiliam.Father = burgerJoe;

         oldBob.Spouse = oldKate;
         oldBob.Children.Add(burgerJoe);
         oldKate.Spouse = oldBob;
         oldKate.Children.Add(burgerJoe);

         oldArnold.Spouse = oldAmber;
         oldArnold.Children.Add(giJane);
         oldAmber.Spouse = oldArnold;
         oldAmber.Children.Add(giJane);
      }


      [TestMethod]
      public void CreateAndCloneAPerson()
      {
         int burgerJoeHash = burgerJoe.GetHashCode();
         Person joeTheClone = burgerJoe.MakeDeepClone();

         Assert.IsFalse(joeTheClone.IsHashCodeCalculated, "The hash code of the cloned person should have been reset due to the use of the attribute [CloneFieldBehaviour(CloneFieldBehaviour.ClearReference)]");
         Assert.AreEqual(joeTheClone.GetHashCode(), burgerJoeHash, "The hash code should have been the same for the original and the clone. Cloning did not work.");
         Assert.AreEqual(joeTheClone, burgerJoe, "joeTheClone and burgerJoe should be value equal.");
         Assert.AreNotSame(burgerJoe, joeTheClone, "joeTheClone is the same instance as burgerJoe! Deep cloning failed.");
         Assert.AreNotSame(burgerJoe.Spouse, joeTheClone.Spouse, "joeTheClone.Spouse is the same instance as burgerJoe.Spouse! Deep cloning failed.");
         Assert.AreSame(joeTheClone, joeTheClone.Spouse.Spouse, "joeTheClone is not the same instance as burgerJoe.Spouse.Spouse! Deep cloning circle reference not handled.");
         Assert.AreNotSame(burgerJoe.Father, joeTheClone.Father, "joeTheClone.Father is the same instance as burgerJoe.Father! Deep cloning failed.");
         Assert.AreNotSame(burgerJoe.Mother, joeTheClone.Mother, "joeTheClone.Mother is the same instance as burgerJoe.Mother! Deep cloning failed.");
         Assert.AreNotSame(burgerJoe.Children, joeTheClone.Children, "joeTheClone.Children is the same instance as burgerJoe.Children! Deep cloning failed.");
         Assert.AreEqual(burgerJoe.Children.Count, joeTheClone.Children.Count, "The Children list sould have the same number of elements.");
         Assert.AreNotSame(burgerJoe.Children[0], joeTheClone.Children[0], "joeTheClone.Children[0] is the same instance as burgerJoe.Children[0]! Deep cloning failed.");
         Assert.AreNotSame(burgerJoe.Children[1], joeTheClone.Children[1], "joeTheClone.Children[1] is the same instance as burgerJoe.Children[1]! Deep cloning failed.");
         Assert.AreSame(joeTheClone.Children[0], joeTheClone.Spouse.Children[0], "joeTheClone.Children[0] is not the same instance as burgerJoe.Spouse.Children[0]! Deep cloning circle reference not handled.");
      }

      [TestMethod]
      public void CloneObjectWithClassInheritance()
      {
         Person emp = new Employee()
         {
            FirstName = "Bret",
            LastName = "Black",
            NickName = "Bret",
            DateOfBirth = new DateTime(1942, 8, 8),
            EmailAddress = "bret@clonefactory.com",
            Manager = new Employee() { NickName = "The Boss" }
         };

         Person empClone = emp.MakeDeepClone();

         Assert.AreNotSame(emp, empClone);
         Assert.AreEqual(emp.GetHashCode(), empClone.GetHashCode(), "The hash code should have been the same for the original and the clone. Cloning did not work.");
         Assert.AreEqual(emp, empClone, "The clone and the original should be value equal. Cloning did not work.");
         Assert.IsTrue(empClone is Employee, "empClone should have been of class Employee.");
         Assert.AreNotSame(emp, empClone, "The instances emp and empClone should not be the same instance.");
         Assert.IsTrue(((Employee)empClone).Manager is Employee, "empClone should have been of class Employee.");
         Assert.AreNotSame(((Employee)emp).Manager, ((Employee)empClone).Manager, "emp.Manager and empClone.Manager should not be the same instance.");
      }

      [TestMethod]
      public void CloneSimpleArrayOfPersons()
      {
         Person[] persons = new Person[]
         {
            burgerJoe,
            giJane,
            oldBob,
            oldKate
         };

         Person[] personsClone = persons.MakeDeepClone();

         Assert.IsTrue(persons.Length == personsClone.Length, "persons.Length != personsClone.Length");
         Assert.AreNotSame(persons, personsClone, "The instances persons and personsClone should not be the same instance.");
         Assert.AreNotSame(persons[0], personsClone[0], "The instances persons[0] and personsClone[0] should not be the same instance.");
         Assert.AreNotSame(persons[1], personsClone[1], "The instances persons[1] and personsClone[1] should not be the same instance.");
         Assert.AreNotSame(persons[2], personsClone[2], "The instances persons[2] and personsClone[2] should not be the same instance.");
         Assert.AreNotSame(persons[3], personsClone[3], "The instances persons[3] and personsClone[3] should not be the same instance.");
      }

      [TestMethod]
      public void CloneSystemArrayOfPersonsWithLowerBound1()
      {
         Array persons = Array.CreateInstance(typeof(Person), new int[] { 4 }, new int[] {1});
         persons.SetValue(burgerJoe, 1);
         persons.SetValue(giJane, 2);
         persons.SetValue(oldBob, 3);
         persons.SetValue(oldKate, 4);

         Array personsClone = persons.MakeDeepClone();
         Assert.IsTrue(persons.Length == personsClone.Length, "persons.Length != personsClone.Length");
         Assert.AreNotSame(persons, personsClone, "The instances persons and personsClone should not be the same instance.");
         Assert.AreNotSame(persons.GetValue(1), personsClone.GetValue(1), "The instances persons[1] and personsClone[1] should not be the same instance.");
         Assert.AreNotSame(persons.GetValue(2), personsClone.GetValue(2), "The instances persons[2] and personsClone[2] should not be the same instance.");
         Assert.AreNotSame(persons.GetValue(3), personsClone.GetValue(3), "The instances persons[3] and personsClone[3] should not be the same instance.");
         Assert.AreNotSame(persons.GetValue(4), personsClone.GetValue(4), "The instances persons[4] and personsClone[4] should not be the same instance.");
      }

      [TestMethod]
      public void CloneMDArrayOfPersons()
      {
         Person[,] persons = new Person[4,2];
         persons[0,0] = burgerJoe;
         persons[1,0] = giJane;
         persons[2,0] = oldBob;
         persons[3,0] = oldKate;
         persons[0, 1] = babyWiliam;
         persons[1, 1] = babyEllen;

         Person[,] personsClone = persons.MakeDeepClone();

         Assert.IsTrue(persons.Length == personsClone.Length, "persons.Length != personsClone.Length");
         Assert.AreNotSame(persons, personsClone, "The instances persons and personsClone should not be the same instance.");
         Assert.AreNotSame(null, personsClone[0, 0], "The personsClone[0,0] should not be null.");
         Assert.AreNotSame(persons[0, 0], personsClone[0, 0], "The instances persons[0,0] and personsClone[0,0] should not be the same instance.");
         Assert.AreNotSame(persons[1,0], personsClone[1,0], "The instances persons[1,0] and personsClone[1,0] should not be the same instance.");
         Assert.AreNotSame(persons[2,0], personsClone[2,0], "The instances persons[2,0] and personsClone[2,0] should not be the same instance.");
         Assert.AreNotSame(persons[3,0], personsClone[3,0], "The instances persons[3,0] and personsClone[3,0] should not be the same instance.");
         Assert.AreNotSame(persons[0, 1], personsClone[0, 1], "The instances persons[0,1] and personsClone[0,1] should not be the same instance.");
         Assert.AreNotSame(persons[1, 1], personsClone[1, 1], "The instances persons[1,1] and personsClone[1,1] should not be the same instance.");
      }

      [TestMethod]
      public void CloneArrayOfInterfacePointers()
      {
         IComparable[] comparables = new IComparable[] { 123.45M, 123.45, 123, DateTime.Now, String.Empty };
         IComparable[] clones = comparables.MakeDeepClone();

         Assert.AreNotSame(comparables, clones, "The clone of the original array should not be the same instance as the original.");
         
         Assert.AreNotSame(comparables[0], clones[0], "The value of comparables[0] and clones[0] should not be the same instance.");
         Assert.AreEqual(comparables[0], clones[0], "The value of comparables[0] and clones[0] should be equal.");

         Assert.AreNotSame(comparables[1], clones[1], "The value of comparables[1] and clones[1] should not be the same instance.");
         Assert.AreEqual(comparables[1], clones[1], "The value of comparables[1] and clones[1] should be equal.");

         Assert.AreNotSame(comparables[2], clones[2], "The value of comparables[2] and clones[2] should not be the same instance.");
         Assert.AreEqual(comparables[2], clones[2], "The value of comparables[2] and clones[2] should be equal.");

         Assert.AreNotSame(comparables[3], clones[3], "The value of comparables[3] and clones[3] should not be the same instance.");
         Assert.AreEqual(comparables[3], clones[3], "The value of comparables[3] and clones[3] should be equal.");

         Assert.AreSame(comparables[4], clones[4], "The value of comparables[4] and clones[4] should be the same instance since strings are imutable!");
         Assert.AreEqual(comparables[4], clones[4], "The value of comparables[4] and clones[4] should be equal.");
      }
   }
}
